package com.dmall.opening.up.records.util;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.PropertyUtils;

import java.math.BigDecimal;
import java.util.*;

import static com.dmall.opening.up.records.util.Numbers.*;

public final class Beans {

    public static String getString(Object bean, String name) {
        return getString(bean, name, null);
    }

    public static String getString(Object bean, String name, String defaultValue) {
        Object value = getOrDefault(bean, name, null);
        return (value != null) ? value.toString() : defaultValue;
    }

    public static Boolean getBoolean(Object bean, String name) {
        return getBoolean(bean, name, Boolean.FALSE);
    }

    public static Boolean getBoolean(Object bean, String name, Boolean defaultValue) {
        Object value = getOrDefault(bean, name, null);
        if (value == null) {
            return defaultValue;
        }
        if (value instanceof Boolean) {
            return (Boolean) value;
        }
        if (value instanceof String) {
            return "true".equalsIgnoreCase((String) value) || "1".equals(value);
        }
        if (value instanceof Number) {
            return 1 == ((Number) value).intValue();
        }
        throw new UnsupportedOperationException();
    }

    public static Object getObject(Object bean, String name) {
        return getOrDefault(bean, name, null);
    }

    public static <T> T get(Object bean, String name) {
        return getOrDefault(bean, name, null);
    }

    public static Integer getInt(Object bean, String name) {
        return toInt(get(bean, name), null);
    }

    public static Integer getInt(Object bean, String name, Integer defaultValue) {
        return toInt(get(bean, name), defaultValue);
    }

    public static Long getLong(Object bean, String name) {
        return toLong(get(bean, name), null);
    }

    public static Long getLong(Object bean, String name, Long defaultValue) {
        return toLong(get(bean, name), defaultValue);
    }

    public static Double getDouble(Object bean, String name) {
        return toDouble(get(bean, name), null);
    }

    public static BigDecimal getBigDecimal(Object bean, String name) {
        return toBigDecimal(get(bean, name), null);
    }

    public static BigDecimal getBigDecimal(Object bean, String name, BigDecimal defaultValue) {
        return toBigDecimal(get(bean, name), defaultValue);
    }

    public static Map<String, Object> getMap(Object bean, String name) {
        return getMap(bean, name, Collections.<String, Object>emptyMap());
    }

    public static Map<String, Object> getMap(Object bean, String name, Map<String, Object> defaultValue) {
        try {
            Map<String, Object> value = (Map<String, Object>) PropertyUtils.getProperty(bean, name);
            return (value != null) ? value : defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static List<Map<String, Object>> getList(Object bean, String name) {
        return getList(bean, name, Collections.<Map<String, Object>>emptyList());
    }

    public static List<Map<String, Object>> getList(Object bean, String name, List<Map<String, Object>> defaultValue) {
        try {
            List<Map<String, Object>> value = (List<Map<String, Object>>) PropertyUtils.getProperty(bean, name);
            return (value != null) ? value : defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static List<String> getStrings(Object bean, String name) {
        try {
            List<Object> value = (List<Object>) PropertyUtils.getProperty(bean, name);
            List<String> result = new ArrayList<>(value.size());
            for (Object o : value) {
                result.add(o.toString());
            }
            return result;
        } catch (Exception e) {
            return Collections.emptyList();
        }
    }

    public static List<Long> getLongs(Object bean, String name) {
        try {
            List<Object> value = (List<Object>) PropertyUtils.getProperty(bean, name);
            List<Long> result = new ArrayList<>(value.size());
            for (Object o : value) {
                result.add(Long.valueOf(o.toString()));
            }
            return result;
        } catch (Exception e) {
            return Collections.emptyList();
        }
    }

    public static List<Object> getObjects(Object bean, String name) {
        try {
            List<Object> value = (List<Object>) PropertyUtils.getProperty(bean, name);
            return (value != null) ? value : new ArrayList<>();
        } catch (Exception e) {
            return new ArrayList<>();
        }
    }

    public static boolean ifPresent(Object bean, String name) {
        return get(bean, name) != null;
    }

    public static void copyProperties(Object dest, Object orig) {
        try {
            BeanUtils.copyProperties(dest, orig);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void copyProperty(final Object dest, final String name, final Object value) {
        try {
            BeanUtils.copyProperty(dest, name, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void addToListProperty(Object bean, String key, Object item) {
        List<Object> value = getObjects(bean, key);
        value.add(item);
        try {
            PropertyUtils.setProperty(bean, key, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void setProperty(Object bean, String key, Object value) {
        try {
            PropertyUtils.setProperty(bean, key, value);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static <T> T getOrDefault(Object bean, String name, T defaultValue) {
        try {
            T value = (T) PropertyUtils.getProperty(bean, name);
            return (value != null) ? value : defaultValue;
        } catch (Exception e) {
            return defaultValue;
        }
    }

    public static Map<String, Object> toMap(Object bean) {
        return toMap(bean, null, null);
    }

    public static Map<String, Object> toMap(Object bean, Map<String, Object> replace, Collection<String> removed) {
        try {
            final Map<String, Object> result = PropertyUtils.describe(bean);
            if (Maps.isNotEmpty(replace)) {
                result.putAll(replace);
            }
            if (removed != null) {
                for (String key : removed) {
                    result.remove(key);
                }
            }
            return result;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static Map<String, Object> toMap(Object bean, Collection<String> removed) {
        return toMap(bean, Collections.emptyMap(), removed);
    }

    public static Map<String, Object> toMap(Object bean, Map<String, Object> replace) {
        return toMap(bean, replace, null);
    }
}
